/*
 * Copyright (c) 2014. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

package com.net.NettyEngine4;


import com.dc.gameserver.ServerCore.Controller.AbstractController.IController;
import com.dc.gameserver.ServerCore.Service.character.PlayerInstance;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.MessageLite;
import com.google.protobuf.UninitializedMessageException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;

import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {

    public static final Logger logger =
            LoggerFactory.getLogger(ServerHandler.class);

    /**
     * 记录服务器在线人数--连接数
     */
    private static final AtomicInteger countConnection = new AtomicInteger(0);

    public static BeanFactory springContext;  //spring context   manager by spring

    private PlayerInstance player;

    public ServerHandler() {
    }

    /**
     * 添加监听器，在指定的时间发送消息
     * Calls {@link ChannelHandlerContext#fireChannelActive()} to forward
     * to the next {@link io.netty.channel.ChannelInboundHandler} in the {@link ChannelPipeline}.
     * <p/>
     * Sub-classes may override this method to change behavior.
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        countConnection.getAndIncrement();// +1
        player = (PlayerInstance) ServerHandler.springContext.getBean("player");
        // player.setChannel(ctx.channel());
        if (logger.isDebugEnabled())
            logger.debug(ctx.channel() + "connect server... Concurrent  connection... " + countConnection.get());
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward
     * to the next {@link io.netty.channel.ChannelInboundHandler} in the {@link ChannelPipeline}.
     * 每一个channel 对应 一个RecyclableArrayList对象，作为该channel的缓冲数据队列；
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
        ByteBuf msg = (ByteBuf) message;
        /**
         *     if channel inactive  ,the finally  callback method{@link io.netty.handler.codec.ByteToMessageDecoder#channelInactive(ChannelHandlerContext)}
         *     this method will do some clear work ,if Recycle ArrayList is not empty ,so will invoke method channelRead,and release byteBuffer,
         *     every task(byteBuffer data which was store blockingQueue will be executor by the "逻辑线程池" ,no matter what it's status)  ,
         *     so if the channel  inactive ,we will clear some buf ,in case of memory crash application,like this :
         */
        if (!ctx.channel().isActive()) {     //inactive status , release buffer
            msg.release();
            msg = null;
        } else {
            try {
                /**类型ID**/
                int ID = msg.readInt();
                /**将byte[]转化为pb实体映射数据**/
                int length = msg.readableBytes();
                if (length != 0) {
                    byte[] array = new byte[length];
                    msg.getBytes(msg.readerIndex(), array, 0, length);
                    msg.release();  //release byte buffer
                    msg = null;  //collection by GC directly
                    MessageLite messageLite =
                            IController.MESSAGE_LITE[ID].getParserForType().parseFrom(array, 0, length, ExtensionRegistryLite.getEmptyRegistry());
                    array = null; //collection by GC directly
                    logger.debug("接收到消息 &( ^___^ )&  -->" + messageLite.toString() + "ID:" + ID);
                    IController.GAME_CONTROLLERS[ID].DispatchMessageLit(messageLite, player);
                } else {
                    msg.release();
                    msg = null;
                    /**消息实体为null*/
                    IController.GAME_CONTROLLERS[ID].DispatchMessageLit(null, player);
                }
            } catch (UninitializedMessageException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelInactive()} to forward
     * to the next {@link Channel} in the {@link ChannelPipeline}.
     * <p/>
     * Sub-classes may override this method to change behavior.
     * <p/>
     * 注意回收资源
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        try {
            ctx.channel().close();
            //clear and store data!  channel closed!
            // this.player.saveAndDestory();

            countConnection.decrementAndGet();// -1

            if (logger.isDebugEnabled())
                logger.debug(ctx.channel().remoteAddress() + " channelClosed , Concurrent  connection... " + countConnection.get());

            ChannelPipeline channelPipeline = ctx.channel().pipeline();

            if (null == channelPipeline) {   // if null ,will be returned
                return;
            }
            //remove pipeline object
            while (channelPipeline.last() != null) {
                channelPipeline.removeLast();
            }
            if (ctx.isRemoved()) {
                ctx.close();
                // Set to null so the GC can collect them directly
                channelPipeline = null;
                ctx = null;
                player = null;
            }
        } catch (NoSuchElementException ee) {
            //do nothing
        } catch (Exception e) {
            if (logger.isDebugEnabled()) logger.error("", e);
            //do nothing
        }
    }

    /**
     * 心跳处理
     * 链路读写超时处理
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        Channel channel = ctx.channel();
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent e = (IdleStateEvent) evt;
            //No data was received for a while.
            if (e.state() == IdleState.READER_IDLE) {
                //you can do sth ,such as timeout
                channel.close();  //call back channelInactive(ChannelHandlerContext ctx)
                if (logger.isDebugEnabled()) logger
                        .debug(channel.remoteAddress() + "---No data was received for a while ,read time out... ...");
            } //	  because we are attaching  more importance to the read_idle time(timeout to rec)
            else if (e.state() == IdleState.WRITER_IDLE) { // No data was sent for a while.
                channel.close();
                if (logger.isDebugEnabled()) logger
                        .debug(channel.remoteAddress() + "---No data was sent for a while.write time out... ...");
            }
        } else if (evt instanceof ChannelInputShutdownEvent) {     // remote peer had close channel
            channel.close();
        }
    }

    /**
     * 获取在线人数
     *
     * @return player  num
     */
    public static int getOnlinePlayers() {
        return countConnection.get();
    }

    /**
     * Sub-classes may override this method to change behavior.
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
//        try {
////            ctx.channel().close();
////            ctx.close();
//        }catch (Exception e){
//            e.printStackTrace();
//        }

    }

    /**
     * spring context
     **/
    public static void setSpringContext(BeanFactory springContext) {
        ServerHandler.springContext = springContext;
    }


}
